package com.util.xls;

import com.google.common.base.Strings;
import org.apache.commons.beanutils.PropertyUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;

/**
 * @auther carter
 * create time    2018-06-07
 * csv格式的导出封装；
 */
public final   class CsvUtils {

    private static final Logger logger = LoggerFactory.getLogger(CsvUtils.class);
    private static final String CHARSET_GBK = "gbk";
    public static final String CHARSET_ISO8859_1 = "iso8859-1";

    /**
     * csv导出
     * @param fileName 导出的文件名
     * @param response web响应对象
     * @param dataList 数据列表
     * @param headMap  头Map  key:字段名字  value:列头名字
     */
    public static<T> void  csvExport(String fileName, HttpServletResponse response, List<T> dataList, LinkedHashMap<String,String> headMap)
    {
        String content = prepareContent(dataList,headMap).toString();
        byte[] contentByteArray = parseByteArray(content);
        setHeadAttr(fileName, response, contentByteArray.length);
        writeContent(response, contentByteArray);


    }

    /**
     * 通过流，把csv文件写到客户端
     * @param response  响应的句柄
     * @param contentByteArray  csv的字节数组
     */
    private static void writeContent(HttpServletResponse response, byte[] contentByteArray) {
        ServletOutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            outputStream.write(contentByteArray,0,contentByteArray.length);
            outputStream.flush();
        } catch (IOException e) {
            logger.error("把csv文件写到客户端异常",e);
        }finally {
            closeOutputStream(outputStream);
        }
    }

    /**
     * 设置响应的header属性
     * @param fileName  csv的文件名称
     * @param response  响应的句柄
     * @param contentByteArrayLength  csv的字节数组长度
     */
    private static void setHeadAttr(String fileName, HttpServletResponse response, int contentByteArrayLength) {
        response.reset();
        response.resetBuffer();
        response.setContentType("application/octet-stream");
        response.setCharacterEncoding(CHARSET_GBK);
        response.setHeader("Content-Length", String.valueOf(contentByteArrayLength));

        String fileNameIso8858_1 =getIso8859_1FileName(fileName);
        response.setHeader("Content-Disposition", "attachment;filename=" + fileNameIso8858_1 + ".csv");
    }

    /**
     * 关闭流，并使得jvm主动回收它；
     * @param outputStream 响应的流对象
     */
    private static void closeOutputStream(ServletOutputStream outputStream) {
        if(outputStream != null) {
            try {
                outputStream.close();
            } catch (IOException var5) {
            } finally {
                outputStream = null;
            }
        }
    }

    /**
     * 文件名称转码
     * @param fileName 文件名
     * @return
     */
    private static String getIso8859_1FileName(String fileName) {
        String fileNameIso8858_1= "";
        try {
             fileNameIso8858_1 = new String(fileName.getBytes(CHARSET_GBK), CHARSET_ISO8859_1);
        } catch (UnsupportedEncodingException e) {
            logger.error("gbk转码成iso8859-1失败",e);
        }
        return fileNameIso8858_1;
    }

    /**
     * csv内容转码
     * @param content csv内容
     * @return
     */
    private static byte[]  parseByteArray(String content) {
        Assert.isTrue(!Strings.isNullOrEmpty(content),"文件内容不能为空");

        byte[] contentByteArray = null;
        try {
            contentByteArray =  content.getBytes(CHARSET_GBK);
        } catch (UnsupportedEncodingException e) {
            logger.error("不支持的编码转换操作",e);
        }
        return contentByteArray;
    }

    /**
     * 准备好csv文件需要的内容
     * @param dataList  数据列表
     * @param headMap   头map
     * @return
     */
    private static<T> StringBuilder prepareContent(List<T> dataList, LinkedHashMap<String, String> headMap) {


        StringBuilder contentBuilder = new StringBuilder();

        if (Objects.isNull(dataList) || dataList.isEmpty())
        {
            contentBuilder.append("\t无数据\n");
            return  contentBuilder;
        }

       Assert.isTrue(Objects.nonNull(headMap) && !headMap.isEmpty(),"列头map不能为空");
       headMap.keySet().forEach(key-> Assert.isTrue(!Strings.isNullOrEmpty(key),"key不能为空"));
       headMap.values().forEach(value-> Assert.isTrue(!Strings.isNullOrEmpty(value),"value不能为空"));

        //先得到列头信息；
        prepareCsvHeadTitle(headMap, contentBuilder);
        prepareHeadData(dataList, headMap, contentBuilder);

        return contentBuilder;
    }

    /**
     * 准备csv文件头的信息
     * @param dataList
     * @param headMap
     * @param contentBuilder
     */
    private static<T> void prepareHeadData(List<T> dataList, LinkedHashMap<String, String> headMap, StringBuilder contentBuilder) {
        dataList.stream().forEachOrdered(data->{

            headMap.entrySet()
                    .stream()
                    .forEachOrdered(headEntry->{

                     String propertyName = headEntry.getKey();
                     String propertyValueString  = getPropertyValue(data, propertyName);
                     contentBuilder.append(propertyValueString+",");

            });
            contentBuilder.append("\r\n");
        });
    }

    /**
     * 通过反射的方式从对象中获取属性值
     * @param data  数据对象
     * @param propertyName 属性名称
     * @return
     */
    private static String getPropertyValue(Object data, String propertyName) {

        String propertyValueString = "";
        try {
            Object propertyValue  = PropertyUtils.getProperty(data, propertyName);
            if (Objects.nonNull(propertyValue))
            {
                propertyValueString = String.valueOf(propertyValue);
            }

        } catch (Exception e) {
           logger.error("通过反射的方式获取数据列中的属性值异常",e);
        }
        return propertyValueString;
    }

    /**
     * 准备好csv文件的头部信息
     * @param headMap  头部的map
     * @param contentBuilder  内容对象
     */
    private static void prepareCsvHeadTitle(LinkedHashMap<String, String> headMap, StringBuilder contentBuilder) {
        headMap.entrySet()
                .stream()
                .forEachOrdered(entry->{
           String title = entry.getValue();
            contentBuilder.append("\t" + title + ",");
       });

        contentBuilder.append("\r\n");
    }


}
